/**
 * Copyright (C) 2023 maminjie <canpool@163.com>
 * Copyright (C) 2017 Uwe Kindler
 * SPDX-License-Identifier: LGPL-2.1
 **/

#include "QxDockFloatingContainer.h"
#include "QxDockAreaWidget.h"
#include "QxDockContainerWidget.h"
#include "QxDockManager.h"
#include "QxDockOverlay.h"
#include "QxDockWidget.h"

#include <QAbstractButton>
#include <QAction>
#include <QApplication>
#include <QBoxLayout>
#include <QDebug>
#include <QElapsedTimer>
#include <QMouseEvent>
#include <QPointer>
#include <QTime>

#include <iostream>

#ifdef Q_OS_WIN
#include <windows.h>
#ifdef _MSC_VER
#pragma comment(lib, "User32.lib")
#endif
#endif
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
#include "linux/QxDockFloatingWidgetTitleBar.h"
#include <xcb/xcb.h>
#endif

QX_BEGIN_NAMESPACE

#ifdef Q_OS_WIN
#if 0   // set to 1 if you need this function for debugging
/**
 * Just for debuging to convert windows message identifiers to strings
 */
static const char* windowsMessageString(int MessageId)
{
	switch (MessageId)
	{
    case 0: return "WM_NULL";
    case 1: return "WM_CREATE";
    case 2: return "WM_DESTROY";
    case 3: return "WM_MOVE";
    case 5: return "WM_SIZE";
    case 6: return "WM_ACTIVATE";
    case 7: return "WM_SETFOCUS";
    case 8: return "WM_KILLFOCUS";
    case 10: return "WM_ENABLE";
    case 11: return "WM_SETREDRAW";
    case 12: return "WM_SETTEXT";
    case 13: return "WM_GETTEXT";
    case 14: return "WM_GETTEXTLENGTH";
    case 15: return "WM_PAINT";
    case 16: return "WM_CLOSE";
    case 17: return "WM_QUERYENDSESSION";
    case 18: return "WM_QUIT";
    case 19: return "WM_QUERYOPEN";
    case 20: return "WM_ERASEBKGND";
    case 21: return "WM_SYSCOLORCHANGE";
    case 22: return "WM_ENDSESSION";
    case 24: return "WM_SHOWWINDOW";
    case 25: return "WM_CTLCOLOR";
    case 26: return "WM_WININICHANGE";
    case 27: return "WM_DEVMODECHANGE";
    case 28: return "WM_ACTIVATEAPP";
    case 29: return "WM_FONTCHANGE";
    case 30: return "WM_TIMECHANGE";
    case 31: return "WM_CANCELMODE";
    case 32: return "WM_SETCURSOR";
    case 33: return "WM_MOUSEACTIVATE";
    case 34: return "WM_CHILDACTIVATE";
    case 35: return "WM_QUEUESYNC";
    case 36: return "WM_GETMINMAXINFO";
    case 38: return "WM_PAINTICON";
    case 39: return "WM_ICONERASEBKGND";
    case 40: return "WM_NEXTDLGCTL";
    case 42: return "WM_SPOOLERSTATUS";
    case 43: return "WM_DRAWITEM";
    case 44: return "WM_MEASUREITEM";
    case 45: return "WM_DELETEITEM";
    case 46: return "WM_VKEYTOITEM";
    case 47: return "WM_CHARTOITEM";
    case 48: return "WM_SETFONT";
    case 49: return "WM_GETFONT";
    case 50: return "WM_SETHOTKEY";
    case 51: return "WM_GETHOTKEY";
    case 55: return "WM_QUERYDRAGICON";
    case 57: return "WM_COMPAREITEM";
    case 61: return "WM_GETOBJECT";
    case 65: return "WM_COMPACTING";
    case 68: return "WM_COMMNOTIFY";
    case 70: return "WM_WINDOWPOSCHANGING";
    case 71: return "WM_WINDOWPOSCHANGED";
    case 72: return "WM_POWER";
    case 73: return "WM_COPYGLOBALDATA";
    case 74: return "WM_COPYDATA";
    case 75: return "WM_CANCELJOURNAL";
    case 78: return "WM_NOTIFY";
    case 80: return "WM_INPUTLANGCHANGEREQUEST";
    case 81: return "WM_INPUTLANGCHANGE";
    case 82: return "WM_TCARD";
    case 83: return "WM_HELP";
    case 84: return "WM_USERCHANGED";
    case 85: return "WM_NOTIFYFORMAT";
    case 123: return "WM_CONTEXTMENU";
    case 124: return "WM_STYLECHANGING";
    case 125: return "WM_STYLECHANGED";
    case 126: return "WM_DISPLAYCHANGE";
    case 127: return "WM_GETICON";
    case 128: return "WM_SETICON";
    case 129: return "WM_NCCREATE";
    case 130: return "WM_NCDESTROY";
    case 131: return "WM_NCCALCSIZE";
    case 132: return "WM_NCHITTEST";
    case 133: return "WM_NCPAINT";
    case 134: return "WM_NCACTIVATE";
    case 135: return "WM_GETDLGCODE";
    case 136: return "WM_SYNCPAINT";
    case 160: return "WM_NCMOUSEMOVE";
    case 161: return "WM_NCLBUTTONDOWN";
    case 162: return "WM_NCLBUTTONUP";
    case 163: return "WM_NCLBUTTONDBLCLK";
    case 164: return "WM_NCRBUTTONDOWN";
    case 165: return "WM_NCRBUTTONUP";
    case 166: return "WM_NCRBUTTONDBLCLK";
    case 167: return "WM_NCMBUTTONDOWN";
    case 168: return "WM_NCMBUTTONUP";
    case 169: return "WM_NCMBUTTONDBLCLK";
    case 171: return "WM_NCXBUTTONDOWN";
    case 172: return "WM_NCXBUTTONUP";
    case 173: return "WM_NCXBUTTONDBLCLK";
    case 176: return "EM_GETSEL";
    case 177: return "EM_SETSEL";
    case 178: return "EM_GETRECT";
    case 179: return "EM_SETRECT";
    case 180: return "EM_SETRECTNP";
    case 181: return "EM_SCROLL";
    case 182: return "EM_LINESCROLL";
    case 183: return "EM_SCROLLCARET";
    case 185: return "EM_GETMODIFY";
    case 187: return "EM_SETMODIFY";
    case 188: return "EM_GETLINECOUNT";
    case 189: return "EM_LINEINDEX";
    case 190: return "EM_SETHANDLE";
    case 191: return "EM_GETHANDLE";
    case 192: return "EM_GETTHUMB";
    case 193: return "EM_LINELENGTH";
    case 194: return "EM_REPLACESEL";
    case 195: return "EM_SETFONT";
    case 196: return "EM_GETLINE";
    case 197: return "EM_LIMITTEXT / EM_SETLIMITTEXT";
    case 198: return "EM_CANUNDO";
    case 199: return "EM_UNDO";
    case 200: return "EM_FMTLINES";
    case 201: return "EM_LINEFROMCHAR";
    case 202: return "EM_SETWORDBREAK";
    case 203: return "EM_SETTABSTOPS";
    case 204: return "EM_SETPASSWORDCHAR";
    case 205: return "EM_EMPTYUNDOBUFFER";
    case 206: return "EM_GETFIRSTVISIBLELINE";
    case 207: return "EM_SETREADONLY";
    case 209: return "EM_SETWORDBREAKPROC / EM_GETWORDBREAKPROC";
    case 210: return "EM_GETPASSWORDCHAR";
    case 211: return "EM_SETMARGINS";
    case 212: return "EM_GETMARGINS";
    case 213: return "EM_GETLIMITTEXT";
    case 214: return "EM_POSFROMCHAR";
    case 215: return "EM_CHARFROMPOS";
    case 216: return "EM_SETIMESTATUS";
    case 217: return "EM_GETIMESTATUS";
    case 224: return "SBM_SETPOS";
    case 225: return "SBM_GETPOS";
    case 226: return "SBM_SETRANGE";
    case 227: return "SBM_GETRANGE";
    case 228: return "SBM_ENABLE_ARROWS";
    case 230: return "SBM_SETRANGEREDRAW";
    case 233: return "SBM_SETSCROLLINFO";
    case 234: return "SBM_GETSCROLLINFO";
    case 235: return "SBM_GETSCROLLBARINFO";
    case 240: return "BM_GETCHECK";
    case 241: return "BM_SETCHECK";
    case 242: return "BM_GETSTATE";
    case 243: return "BM_SETSTATE";
    case 244: return "BM_SETSTYLE";
    case 245: return "BM_CLICK";
    case 246: return "BM_GETIMAGE";
    case 247: return "BM_SETIMAGE";
    case 248: return "BM_SETDONTCLICK";
    case 255: return "WM_INPUT";
    case 256: return "WM_KEYDOWN";
    case 257: return "WM_KEYUP";
    case 258: return "WM_CHAR";
    case 259: return "WM_DEADCHAR";
    case 260: return "WM_SYSKEYDOWN";
    case 261: return "WM_SYSKEYUP";
    case 262: return "WM_SYSCHAR";
    case 263: return "WM_SYSDEADCHAR";
    case 265: return "WM_UNICHAR / WM_WNT_CONVERTREQUESTEX";
    case 266: return "WM_CONVERTREQUEST";
    case 267: return "WM_CONVERTRESULT";
    case 268: return "WM_INTERIM";
    case 269: return "WM_IME_STARTCOMPOSITION";
    case 270: return "WM_IME_ENDCOMPOSITION";
    case 272: return "WM_INITDIALOG";
    case 273: return "WM_COMMAND";
    case 274: return "WM_SYSCOMMAND";
    case 275: return "WM_TIMER";
    case 276: return "WM_HSCROLL";
    case 277: return "WM_VSCROLL";
    case 278: return "WM_INITMENU";
    case 279: return "WM_INITMENUPOPUP";
    case 280: return "WM_SYSTIMER";
    case 287: return "WM_MENUSELECT";
    case 288: return "WM_MENUCHAR";
    case 289: return "WM_ENTERIDLE";
    case 290: return "WM_MENURBUTTONUP";
    case 291: return "WM_MENUDRAG";
    case 292: return "WM_MENUGETOBJECT";
    case 293: return "WM_UNINITMENUPOPUP";
    case 294: return "WM_MENUCOMMAND";
    case 295: return "WM_CHANGEUISTATE";
    case 296: return "WM_UPDATEUISTATE";
    case 297: return "WM_QUERYUISTATE";
    case 306: return "WM_CTLCOLORMSGBOX";
    case 307: return "WM_CTLCOLOREDIT";
    case 308: return "WM_CTLCOLORLISTBOX";
    case 309: return "WM_CTLCOLORBTN";
    case 310: return "WM_CTLCOLORDLG";
    case 311: return "WM_CTLCOLORSCROLLBAR";
    case 312: return "WM_CTLCOLORSTATIC";
    case 512: return "WM_MOUSEMOVE";
    case 513: return "WM_LBUTTONDOWN";
    case 514: return "WM_LBUTTONUP";
    case 515: return "WM_LBUTTONDBLCLK";
    case 516: return "WM_RBUTTONDOWN";
    case 517: return "WM_RBUTTONUP";
    case 518: return "WM_RBUTTONDBLCLK";
    case 519: return "WM_MBUTTONDOWN";
    case 520: return "WM_MBUTTONUP";
    case 521: return "WM_MBUTTONDBLCLK";
    case 522: return "WM_MOUSEWHEEL";
    case 523: return "WM_XBUTTONDOWN";
    case 524: return "WM_XBUTTONUP";
    case 525: return "WM_XBUTTONDBLCLK";
    case 528: return "WM_PARENTNOTIFY";
    case 529: return "WM_ENTERMENULOOP";
    case 530: return "WM_EXITMENULOOP";
    case 531: return "WM_NEXTMENU";
    case 532: return "WM_SIZING";
    case 533: return "WM_CAPTURECHANGED";
    case 534: return "WM_MOVING";
    case 536: return "WM_POWERBROADCAST";
    case 537: return "WM_DEVICECHANGE";
    case 544: return "WM_MDICREATE";
    case 545: return "WM_MDIDESTROY";
    case 546: return "WM_MDIACTIVATE";
    case 547: return "WM_MDIRESTORE";
    case 548: return "WM_MDINEXT";
    case 549: return "WM_MDIMAXIMIZE";
    case 550: return "WM_MDITILE";
    case 551: return "WM_MDICASCADE";
    case 552: return "WM_MDIICONARRANGE";
    case 553: return "WM_MDIGETACTIVE";
    case 560: return "WM_MDISETMENU";
    case 561: return "WM_ENTERSIZEMOVE";
    case 562: return "WM_EXITSIZEMOVE";
    case 563: return "WM_DROPFILES";
    case 564: return "WM_MDIREFRESHMENU";
    case 640: return "WM_IME_REPORT";
    case 641: return "WM_IME_SETCONTEXT";
    case 642: return "WM_IME_NOTIFY";
    case 643: return "WM_IME_CONTROL";
    case 644: return "WM_IME_COMPOSITIONFULL";
    case 645: return "WM_IME_SELECT";
    case 646: return "WM_IME_CHAR";
    case 648: return "WM_IME_REQUEST";
    case 656: return "WM_IME_KEYDOWN";
    case 657: return "WM_IME_KEYUP";
    case 672: return "WM_NCMOUSEHOVER";
    case 673: return "WM_MOUSEHOVER";
    case 674: return "WM_NCMOUSELEAVE";
    case 675: return "WM_MOUSELEAVE";
    case 768: return "WM_CUT";
    case 769: return "WM_COPY";
    case 770: return "WM_PASTE";
    case 771: return "WM_CLEAR";
    case 772: return "WM_UNDO";
    case 773: return "WM_RENDERFORMAT";
    case 774: return "WM_RENDERALLFORMATS";
    case 775: return "WM_DESTROYCLIPBOARD";
    case 776: return "WM_DRAWCLIPBOARD";
    case 777: return "WM_PAINTCLIPBOARD";
    case 778: return "WM_VSCROLLCLIPBOARD";
    case 779: return "WM_SIZECLIPBOARD";
    case 780: return "WM_ASKCBFORMATNAME";
    case 781: return "WM_CHANGECBCHAIN";
    case 782: return "WM_HSCROLLCLIPBOARD";
    case 783: return "WM_QUERYNEWPALETTE";
    case 784: return "WM_PALETTEISCHANGING";
    case 785: return "WM_PALETTECHANGED";
    case 786: return "WM_HOTKEY";
    case 791: return "WM_PRINT";
    case 792: return "WM_PRINTCLIENT";
    case 793: return "WM_APPCOMMAND";
    case 856: return "WM_HANDHELDFIRST";
    case 863: return "WM_HANDHELDLAST";
    case 864: return "WM_AFXFIRST";
    case 895: return "WM_AFXLAST";
    case 896: return "WM_PENWINFIRST";
    case 897: return "WM_RCRESULT";
    case 898: return "WM_HOOKRCRESULT";
    case 899: return "WM_GLOBALRCCHANGE / WM_PENMISCINFO";
    case 900: return "WM_SKB";
    case 901: return "WM_HEDITCTL / WM_PENCTL";
    case 902: return "WM_PENMISC";
    case 903: return "WM_CTLINIT";
    case 904: return "WM_PENEVENT";
    case 911: return "WM_PENWINLAST";
    default:
    	return "unknown WM_ message";
	}

	return "unknown WM_ message";
}
#endif
#endif

static unsigned int zOrderCounter = 0;
/**
 * Private data class of DockFloatingContainer class (pimpl)
 */
struct DockFloatingContainerPrivate {
    DockFloatingContainer *_this;
    DockContainerWidget *DockContainer;
    unsigned int zOrderIndex = ++zOrderCounter;
    QPointer<DockManager> dockManager;
    eDragState DraggingState = DraggingInactive;
    QPoint DragStartMousePosition;
    DockContainerWidget *DropContainer = nullptr;
    DockAreaWidget *SingleDockArea = nullptr;
    QPoint DragStartPos;
    bool Hiding = false;
    bool AutoHideChildren = true;
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    QWidget *MouseEventHandler = nullptr;
    DockFloatingWidgetTitleBar *TitleBar = nullptr;
    bool IsResizing = false;
#endif

    /**
     * Private data constructor
     */
    DockFloatingContainerPrivate(DockFloatingContainer *_public);

    void titleMouseReleaseEvent();
    void updateDropOverlays(const QPoint &GlobalPos);

    /**
     * Returns true if the given config flag is set
     */
    static bool testConfigFlag(DockManager::eConfigFlag Flag)
    {
        return DockManager::testConfigFlag(Flag);
    }

    /**
     * Tests is a certain state is active
     */
    bool isState(eDragState StateId) const
    {
        return StateId == DraggingState;
    }

    /**
     * Sets the dragging state and posts a FloatingWidgetDragStartEvent
     * if dragging starts
     */
    void setState(eDragState StateId)
    {
        if (DraggingState == StateId) {
            return;
        }

        DraggingState = StateId;
        if (DraggingFloatingWidget == DraggingState) {
            qApp->postEvent(_this, new QEvent((QEvent::Type)internal::FloatingWidgetDragStartEvent));
        }
    }

    void setWindowTitle(const QString &Text)
    {
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
        if (TitleBar) {
            TitleBar->setTitle(Text);
        }
#endif
        _this->setWindowTitle(Text);
    }

    /**
     * Reflect the current dock widget title in the floating widget windowTitle()
     * depending on the DockManager::FloatingContainerHasWidgetTitle flag
     */
    void reflectCurrentWidget(DockWidget *CurrentWidget)
    {
        // reflect CurrentWidget's title if configured to do so, otherwise display application name as window title
        if (testConfigFlag(DockManager::FloatingContainerHasWidgetTitle)) {
            setWindowTitle(CurrentWidget->windowTitle());
        } else {
            setWindowTitle(floatingContainersTitle());
        }

        // reflect CurrentWidget's icon if configured to do so, otherwise display application icon as window icon
        QIcon CurrentWidgetIcon = CurrentWidget->icon();
        if (testConfigFlag(DockManager::FloatingContainerHasWidgetIcon) && !CurrentWidgetIcon.isNull()) {
            _this->setWindowIcon(CurrentWidget->icon());
        } else {
            _this->setWindowIcon(QApplication::windowIcon());
        }
    }

    /**
     * Handles escape key press when dragging around the floating widget
     */
    void handleEscapeKey();

    /**
     * Returns the title used by all FloatingContainer that does not
     * reflect the title of the current dock widget.
     *
     * If not title was set with DockManager::setFloatingContainersTitle(),
     * it returns QGuiApplication::applicationDisplayName().
     */
    static QString floatingContainersTitle()
    {
        return DockManager::floatingContainersTitle();
    }
};
// struct DockFloatingContainerPrivate

DockFloatingContainerPrivate::DockFloatingContainerPrivate(DockFloatingContainer *_public) : _this(_public)
{
}

void DockFloatingContainerPrivate::titleMouseReleaseEvent()
{
    setState(DraggingInactive);
    if (!DropContainer) {
        return;
    }

    if (dockManager->dockAreaOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea ||
        dockManager->containerOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea) {
        DockOverlay *Overlay = dockManager->containerOverlay();
        if (!Overlay->dropOverlayRect().isValid()) {
            Overlay = dockManager->dockAreaOverlay();
        }

        // Resize the floating widget to the size of the highlighted drop area
        // rectangle
        QRect Rect = Overlay->dropOverlayRect();
        int FrameWidth = (_this->frameSize().width() - _this->rect().width()) / 2;
        int TitleBarHeight = _this->frameSize().height() - _this->rect().height() - FrameWidth;
        if (Rect.isValid()) {
            QPoint TopLeft = Overlay->mapToGlobal(Rect.topLeft());
            TopLeft.ry() += TitleBarHeight;
            _this->setGeometry(QRect(TopLeft, QSize(Rect.width(), Rect.height() - TitleBarHeight)));
            QApplication::processEvents();
        }
        DropContainer->dropFloatingWidget(_this, QCursor::pos());
    }

    dockManager->containerOverlay()->hideOverlay();
    dockManager->dockAreaOverlay()->hideOverlay();
}

void DockFloatingContainerPrivate::updateDropOverlays(const QPoint &GlobalPos)
{
    if (!_this->isVisible() || !dockManager) {
        return;
    }

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    // Prevent display of drop overlays and docking as long as a model dialog
    // is active
    if (qApp->activeModalWidget()) {
        return;
    }
#endif

    auto Containers = dockManager->dockContainers();
    DockContainerWidget *TopContainer = nullptr;
    for (auto ContainerWidget : Containers) {
        if (!ContainerWidget->isVisible()) {
            continue;
        }

        if (DockContainer == ContainerWidget) {
            continue;
        }

        QPoint MappedPos = ContainerWidget->mapFromGlobal(GlobalPos);
        if (ContainerWidget->rect().contains(MappedPos)) {
            if (!TopContainer || ContainerWidget->isInFrontOf(TopContainer)) {
                TopContainer = ContainerWidget;
            }
        }
    }

    DropContainer = TopContainer;
    auto ContainerOverlay = dockManager->containerOverlay();
    auto DockAreaOverlay = dockManager->dockAreaOverlay();

    if (!TopContainer) {
        ContainerOverlay->hideOverlay();
        DockAreaOverlay->hideOverlay();
        return;
    }

    int VisibleDockAreas = TopContainer->visibleDockAreaCount();
    ContainerOverlay->setAllowedAreas(VisibleDockAreas > 1 ? OuterDockAreas : AllDockAreas);
    DockWidgetArea ContainerArea = ContainerOverlay->showOverlay(TopContainer);
    ContainerOverlay->enableDropPreview(ContainerArea != InvalidDockWidgetArea);
    auto dockArea = TopContainer->dockAreaAt(GlobalPos);
    if (dockArea && dockArea->isVisible() && VisibleDockAreas > 0) {
        DockAreaOverlay->enableDropPreview(true);
        DockAreaOverlay->setAllowedAreas((VisibleDockAreas == 1) ? NoDockWidgetArea : dockArea->allowedAreas());
        DockWidgetArea Area = DockAreaOverlay->showOverlay(dockArea);

        // A CenterDockWidgetArea for the dockAreaOverlay() indicates that
        // the mouse is in the title bar. If the ContainerArea is valid
        // then we ignore the dock area of the dockAreaOverlay() and disable
        // the drop preview
        if ((Area == CenterDockWidgetArea) && (ContainerArea != InvalidDockWidgetArea)) {
            DockAreaOverlay->enableDropPreview(false);
            ContainerOverlay->enableDropPreview(true);
        } else {
            ContainerOverlay->enableDropPreview(InvalidDockWidgetArea == Area);
        }
    } else {
        DockAreaOverlay->hideOverlay();
    }
}

void DockFloatingContainerPrivate::handleEscapeKey()
{
    QX_DOCK_PRINT("DockFloatingContainerPrivate::handleEscapeKey()");
    setState(DraggingInactive);
    dockManager->containerOverlay()->hideOverlay();
    dockManager->dockAreaOverlay()->hideOverlay();
}

DockFloatingContainer::DockFloatingContainer(DockManager *dockManager)
    : tFloatingWidgetBase(dockManager), d(new DockFloatingContainerPrivate(this))
{
    d->dockManager = dockManager;
    d->DockContainer = new DockContainerWidget(dockManager, this);
    connect(d->DockContainer, SIGNAL(dockAreasAdded()), this, SLOT(onDockAreasAddedOrRemoved()));
    connect(d->DockContainer, SIGNAL(dockAreasRemoved()), this, SLOT(onDockAreasAddedOrRemoved()));

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    QDockWidget::setWidget(d->DockContainer);
    QDockWidget::setFloating(true);
    QDockWidget::setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable |
                             QDockWidget::DockWidgetFloatable);

    bool native_window = true;

    // FloatingContainerForce*TitleBar is overwritten by the "ADS_UseNativeTitle" environment variable if set.
    auto env = qgetenv("ADS_UseNativeTitle").toUpper();
    if (env == "1") {
        native_window = true;
    } else if (env == "0") {
        native_window = false;
    } else if (dockManager->testConfigFlag(DockManager::FloatingContainerForceNativeTitleBar)) {
        native_window = true;
    } else if (dockManager->testConfigFlag(DockManager::FloatingContainerForceQWidgetTitleBar)) {
        native_window = false;
    } else {
        // KDE doesn't seem to fire MoveEvents while moving windows, so for now no native titlebar for everything using
        // KWin.
        QString window_manager = internal::windowManager().toUpper().split(" ")[0];
        native_window = window_manager != "KWIN";
    }

    if (native_window) {
        // Native windows do not work if wayland is used. Ubuntu 22.04 uses wayland by default. To use
        // native windows, switch to Xorg
        QString XdgSessionType = qgetenv("XDG_SESSION_TYPE").toLower();
        if ("wayland" == XdgSessionType) {
            native_window = false;
        }
    }

    if (native_window) {
        setTitleBarWidget(new QWidget());
        setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint);
    } else {
        d->TitleBar = new DockFloatingWidgetTitleBar(this);
        setTitleBarWidget(d->TitleBar);
        setWindowFlags(Qt::Window | Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint);
        d->TitleBar->enableCloseButton(isClosable());
        connect(d->TitleBar, SIGNAL(closeRequested()), SLOT(close()));
        connect(d->TitleBar, &DockFloatingWidgetTitleBar::maximizeRequested, this,
                &DockFloatingContainer::onMaximizeRequest);
    }
#else
    setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint);
    QBoxLayout *l = new QBoxLayout(QBoxLayout::TopToBottom);
    l->setContentsMargins(0, 0, 0, 0);
    l->setSpacing(0);
    setLayout(l);
    l->addWidget(d->DockContainer);
#endif

    dockManager->registerFloatingWidget(this);
}

DockFloatingContainer::DockFloatingContainer(DockAreaWidget *dockArea)
    : DockFloatingContainer(dockArea->dockManager())
{
    d->DockContainer->addDockArea(dockArea);

    auto TopLevelDockWidget = topLevelDockWidget();
    if (TopLevelDockWidget) {
        TopLevelDockWidget->emitTopLevelChanged(true);
    }

    d->dockManager->notifyWidgetOrAreaRelocation(dockArea);
}

DockFloatingContainer::DockFloatingContainer(DockWidget *dockWidget)
    : DockFloatingContainer(dockWidget->dockManager())
{
    d->DockContainer->addDockWidget(CenterDockWidgetArea, dockWidget);
    auto TopLevelDockWidget = topLevelDockWidget();
    if (TopLevelDockWidget) {
        TopLevelDockWidget->emitTopLevelChanged(true);
    }

    d->dockManager->notifyWidgetOrAreaRelocation(dockWidget);
}

DockFloatingContainer::~DockFloatingContainer()
{
    QX_DOCK_PRINT("~DockFloatingContainer");
    if (d->dockManager) {
        d->dockManager->removeFloatingWidget(this);
    }
    delete d;
}

DockContainerWidget *DockFloatingContainer::dockContainer() const
{
    return d->DockContainer;
}

void DockFloatingContainer::changeEvent(QEvent *event)
{
    Super::changeEvent(event);
    switch (event->type()) {
    case QEvent::ActivationChange:
        if (isActiveWindow()) {
            QX_DOCK_PRINT("FloatingWidget::changeEvent QEvent::ActivationChange ");
            d->zOrderIndex = ++zOrderCounter;

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
            if (d->DraggingState == DraggingFloatingWidget) {
                d->titleMouseReleaseEvent();
                d->DraggingState = DraggingInactive;
            }
#endif
        }
        break;

    case QEvent::WindowStateChange:
        // If the dockManager window is restored from minimized on Windows
        // then the FloatingWidgets are not properly restored to maximized but
        // to normal state.
        // We simply check here, if the FloatingWidget was maximized before
        // and if the dockManager is just leaving the minimized state. In this
        // case, we restore the maximized state of this floating widget
        if (d->dockManager->isLeavingMinimizedState()) {
            QWindowStateChangeEvent *ev = static_cast<QWindowStateChangeEvent *>(event);
            if (ev->oldState().testFlag(Qt::WindowMaximized)) {
                this->showMaximized();
            }
        }
        break;

    default:
        break;   // do nothing
    }
}

#ifdef Q_OS_WIN
    #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
bool DockFloatingContainer::nativeEvent(const QByteArray &eventType, void *message, long *result)
    #else
bool DockFloatingContainer::nativeEvent(const QByteArray &eventType, void *message, qintptr *result)
    #endif
{
    QWidget::nativeEvent(eventType, message, result);
    MSG *msg = static_cast<MSG *>(message);
    switch (msg->message) {
    case WM_MOVING: {
        if (d->isState(DraggingFloatingWidget)) {
            d->updateDropOverlays(QCursor::pos());
        }
    } break;

    case WM_NCLBUTTONDOWN:
        if (msg->wParam == HTCAPTION && d->isState(DraggingInactive)) {
            QX_DOCK_PRINT("DockFloatingContainer::nativeEvent WM_NCLBUTTONDOWN");
            d->DragStartPos = pos();
            d->setState(DraggingMousePressed);
        }
        break;

    case WM_NCLBUTTONDBLCLK:
        d->setState(DraggingInactive);
        break;

    case WM_ENTERSIZEMOVE:
        if (d->isState(DraggingMousePressed)) {
            QX_DOCK_PRINT("DockFloatingContainer::nativeEvent WM_ENTERSIZEMOVE");
            d->setState(DraggingFloatingWidget);
            d->updateDropOverlays(QCursor::pos());
        }
        break;

    case WM_EXITSIZEMOVE:
        if (d->isState(DraggingFloatingWidget)) {
            QX_DOCK_PRINT("DockFloatingContainer::nativeEvent WM_EXITSIZEMOVE");
            if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) {
                d->handleEscapeKey();
            } else {
                d->titleMouseReleaseEvent();
            }
        }
        break;
    }
    return false;
}
#endif

void DockFloatingContainer::closeEvent(QCloseEvent *event)
{
    QX_DOCK_PRINT("DockFloatingContainer closeEvent");
    d->setState(DraggingInactive);
    event->ignore();
    if (!isClosable()) {
        return;
    }

    bool HasOpenDockWidgets = false;
    for (auto dockWidget : d->DockContainer->openedDockWidgets()) {
        if (dockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose) ||
            dockWidget->features().testFlag(DockWidget::CustomCloseHandling)) {
            bool Closed = dockWidget->closeDockWidgetInternal();
            if (!Closed) {
                HasOpenDockWidgets = true;
            }
        } else {
            dockWidget->toggleView(false);
        }
    }

    if (HasOpenDockWidgets) {
        return;
    }

    // In Qt version after 5.9.2 there seems to be a bug that causes the
    // QWidget::event() function to not receive any NonClientArea mouse
    // events anymore after a close/show cycle. The bug is reported here:
    // https://bugreports.qt.io/browse/QTBUG-73295
    // The following code is a workaround for Qt versions > 5.9.2 that seems
    // to work
    // Starting from Qt version 5.12.2 this seems to work again. But
    // now the QEvent::NonClientAreaMouseButtonPress function returns always
    // Qt::RightButton even if the left button was pressed
    this->hide();
}

void DockFloatingContainer::hideEvent(QHideEvent *event)
{
    Super::hideEvent(event);
    if (event->spontaneous()) {
        return;
    }

    // Prevent toogleView() events during restore state
    if (d->dockManager->isRestoringState()) {
        return;
    }

    if (d->AutoHideChildren) {
        d->Hiding = true;
        for (auto dockArea : d->DockContainer->openedDockAreas()) {
            for (auto dockWidget : dockArea->openedDockWidgets()) {
                dockWidget->toggleView(false);
            }
        }
        d->Hiding = false;
    }
}

void DockFloatingContainer::showEvent(QShowEvent *event)
{
    Super::showEvent(event);
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) {
        this->window()->activateWindow();
    }
#endif
}

void DockFloatingContainer::startFloating(const QPoint &DragStartMousePos, const QSize &Size, eDragState DragState,
                                           QWidget *MouseEventHandler)
{
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    if (!isMaximized()) {
        resize(Size);
        d->DragStartMousePosition = DragStartMousePos;
    }
    d->setState(DragState);
    if (DraggingFloatingWidget == DragState) {
        d->MouseEventHandler = MouseEventHandler;
        if (d->MouseEventHandler) {
            d->MouseEventHandler->grabMouse();
        }
    }

    if (!isMaximized()) {
        moveFloating();
    }
    show();
#else
    Q_UNUSED(MouseEventHandler)
    resize(Size);
    d->DragStartMousePosition = DragStartMousePos;
    d->setState(DragState);
    moveFloating();
    show();
#endif
}

void DockFloatingContainer::moveFloating()
{
    int BorderSize = (frameSize().width() - size().width()) / 2;
    const QPoint moveToPos = QCursor::pos() - d->DragStartMousePosition - QPoint(BorderSize, 0);
    move(moveToPos);
    switch (d->DraggingState) {
    case DraggingMousePressed:
        d->setState(DraggingFloatingWidget);
        d->updateDropOverlays(QCursor::pos());
        break;

    case DraggingFloatingWidget:
        d->updateDropOverlays(QCursor::pos());
#ifdef Q_OS_MACOS
        // In OSX when hiding the DockAreaOverlay the application would set
        // the main window as the active window for some reason. This fixes
        // that by resetting the active window to the floating widget after
        // updating the overlays.
        QApplication::setActiveWindow(this);
#endif
        break;
    default:
        break;
    }
}

bool DockFloatingContainer::isClosable() const
{
    return d->DockContainer->features().testFlag(DockWidget::DockWidgetClosable);
}

void DockFloatingContainer::onDockAreasAddedOrRemoved()
{
    QX_DOCK_PRINT("DockFloatingContainer::onDockAreasAddedOrRemoved()");
    auto TopLevelDockArea = d->DockContainer->topLevelDockArea();
    if (TopLevelDockArea) {
        d->SingleDockArea = TopLevelDockArea;
        DockWidget *CurrentWidget = d->SingleDockArea->currentDockWidget();
        d->reflectCurrentWidget(CurrentWidget);
        connect(d->SingleDockArea, SIGNAL(currentChanged(int)), this, SLOT(onDockAreaCurrentChanged(int)));
    } else {
        if (d->SingleDockArea) {
            disconnect(d->SingleDockArea, SIGNAL(currentChanged(int)), this, SLOT(onDockAreaCurrentChanged(int)));
            d->SingleDockArea = nullptr;
        }
        d->setWindowTitle(d->floatingContainersTitle());
        setWindowIcon(QApplication::windowIcon());
    }
}

void DockFloatingContainer::updateWindowTitle()
{
    // If this floating container will be hidden, then updating the window
    // tile is not required anymore
    if (d->Hiding) {
        return;
    }

    auto TopLevelDockArea = d->DockContainer->topLevelDockArea();
    if (TopLevelDockArea) {
        DockWidget *CurrentWidget = TopLevelDockArea->currentDockWidget();
        if (CurrentWidget) {
            d->reflectCurrentWidget(CurrentWidget);
        }
    } else {
        d->setWindowTitle(d->floatingContainersTitle());
        setWindowIcon(QApplication::windowIcon());
    }
}

void DockFloatingContainer::onDockAreaCurrentChanged(int Index)
{
    Q_UNUSED(Index);
    DockWidget *CurrentWidget = d->SingleDockArea->currentDockWidget();
    d->reflectCurrentWidget(CurrentWidget);
}

bool DockFloatingContainer::restoreState(DockStateReader &Stream, bool Testing)
{
    if (!d->DockContainer->restoreState(Stream, Testing)) {
        return false;
    }
    onDockAreasAddedOrRemoved();
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    if (d->TitleBar) {
        d->TitleBar->setMaximizedIcon(windowState() == Qt::WindowMaximized);
    }
#endif
    return true;
}

bool DockFloatingContainer::hasTopLevelDockWidget() const
{
    return d->DockContainer->hasTopLevelDockWidget();
}

DockWidget *DockFloatingContainer::topLevelDockWidget() const
{
    return d->DockContainer->topLevelDockWidget();
}

QList<DockWidget *> DockFloatingContainer::dockWidgets() const
{
    return d->DockContainer->dockWidgets();
}

void DockFloatingContainer::hideAndDeleteLater()
{
    // Widget has been redocked, so it must be hidden right way (see
    // https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/351)
    // but AutoHideChildren must be set to false because "this" still contains
    // dock widgets that shall not be toggled hidden.
    d->AutoHideChildren = false;
    hide();
    deleteLater();
}

void DockFloatingContainer::finishDragging()
{
    QX_DOCK_PRINT("DockFloatingContainer::finishDragging");
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    setWindowOpacity(1);
    activateWindow();
    if (d->MouseEventHandler) {
        d->MouseEventHandler->releaseMouse();
        d->MouseEventHandler = nullptr;
    }
#endif
    d->titleMouseReleaseEvent();
}

#ifdef Q_OS_MACOS

bool DockFloatingContainer::event(QEvent *e)
{
    switch (d->DraggingState) {
    case DraggingInactive: {
        // Normally we would check here, if the left mouse button is pressed.
        // But from QT version 5.12.2 on the mouse events from
        // QEvent::NonClientAreaMouseButtonPress return the wrong mouse button
        // The event always returns Qt::RightButton even if the left button
        // is clicked.
        // It is really great to work around the whole NonClientMouseArea
        // bugs
    #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 2))
        if (e->type() ==
            QEvent::NonClientAreaMouseButtonPress /*&& QGuiApplication::mouseButtons().testFlag(Qt::LeftButton)*/)
    #else
        if (e->type() == QEvent::NonClientAreaMouseButtonPress &&
            QGuiApplication::mouseButtons().testFlag(Qt::LeftButton))
    #endif
        {
            QX_DOCK_PRINT("FloatingWidget::event Event::NonClientAreaMouseButtonPress" << e->type());
            d->DragStartPos = pos();
            d->setState(DraggingMousePressed);
        }
    } break;

    case DraggingMousePressed:
        switch (e->type()) {
        case QEvent::NonClientAreaMouseButtonDblClick:
            QX_DOCK_PRINT("FloatingWidget::event QEvent::NonClientAreaMouseButtonDblClick");
            d->setState(DraggingInactive);
            break;

        case QEvent::Resize:
            // If the first event after the mouse press is a resize event, then
            // the user resizes the window instead of dragging it around.
            // But there is one exception. If the window is maximized,
            // then dragging the window via title bar will cause the widget to
            // leave the maximized state. This in turn will trigger a resize event.
            // To know, if the resize event was triggered by user via moving a
            // corner of the window frame or if it was caused by a windows state
            // change, we check, if we are not in maximized state.
            if (!isMaximized()) {
                d->setState(DraggingInactive);
            }
            break;

        default:
            break;
        }
        break;

    case DraggingFloatingWidget:
        if (e->type() == QEvent::NonClientAreaMouseButtonRelease) {
            QX_DOCK_PRINT("FloatingWidget::event QEvent::NonClientAreaMouseButtonRelease");
            d->titleMouseReleaseEvent();
        }
        break;

    default:
        break;
    }

    #if (QX_DOCK_DEBUG_LEVEL > 0)
    qDebug() << QTime::currentTime() << "DockFloatingContainer::event " << e->type();
    #endif
    return QWidget::event(e);
}

void DockFloatingContainer::moveEvent(QMoveEvent *event)
{
    QWidget::moveEvent(event);
    switch (d->DraggingState) {
    case DraggingMousePressed:
        d->setState(DraggingFloatingWidget);
        d->updateDropOverlays(QCursor::pos());
        break;

    case DraggingFloatingWidget:
        d->updateDropOverlays(QCursor::pos());
        // In OSX when hiding the DockAreaOverlay the application would set
        // the main window as the active window for some reason. This fixes
        // that by resetting the active window to the floating widget after
        // updating the overlays.
        QApplication::setActiveWindow(this);
        break;
    default:
        break;
    }
}
#endif

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)

void DockFloatingContainer::onMaximizeRequest()
{
    if (windowState() == Qt::WindowMaximized) {
        showNormal();
    } else {
        showMaximized();
    }
}

void DockFloatingContainer::showNormal(bool fixGeometry)
{
    if (windowState() == Qt::WindowMaximized) {
        QRect oldNormal = normalGeometry();
        Super::showNormal();
        if (fixGeometry) {
            setGeometry(oldNormal);
        }
    }
    if (d->TitleBar) {
        d->TitleBar->setMaximizedIcon(false);
    }
}

void DockFloatingContainer::showMaximized()
{
    Super::showMaximized();
    if (d->TitleBar) {
        d->TitleBar->setMaximizedIcon(true);
    }
}

bool DockFloatingContainer::isMaximized() const
{
    return windowState() == Qt::WindowMaximized;
}

void DockFloatingContainer::show()
{
    // Prevent this window from showing in the taskbar and pager (alt+tab)
    internal::xcb_add_prop(true, winId(), "_NET_WM_STATE", "_NET_WM_STATE_SKIP_TASKBAR");
    internal::xcb_add_prop(true, winId(), "_NET_WM_STATE", "_NET_WM_STATE_SKIP_PAGER");
    Super::show();
}

void DockFloatingContainer::resizeEvent(QResizeEvent *event)
{
    d->IsResizing = true;
    Super::resizeEvent(event);
}

static bool s_mousePressed = false;

void DockFloatingContainer::moveEvent(QMoveEvent *event)
{
    Super::moveEvent(event);
    if (!d->IsResizing && event->spontaneous() && s_mousePressed) {
        d->setState(DraggingFloatingWidget);
        d->updateDropOverlays(QCursor::pos());
    }
    d->IsResizing = false;
}

bool DockFloatingContainer::event(QEvent *e)
{
    bool result = Super::event(e);
    switch (e->type()) {
    case QEvent::WindowActivate:
        s_mousePressed = false;
        break;
    case QEvent::WindowDeactivate:
        s_mousePressed = true;
        break;
    default:
        break;
    }
    return result;
}

bool DockFloatingContainer::hasNativeTitleBar()
{
    return d->TitleBar == nullptr;
}
#endif

QX_END_NAMESPACE
